"""
Основной файл для управления чатами ВК.

- Чаты-зеркала. Ссылаются на главный чат, копируют его характеристики.
- Сброс данных чата.
- Исключение участников:
  * Вышедших.
  * Тех кто не писал в беседу.
  * От определённого участника.
- Упралвение чёрным списком:
  * Участники чёрного списка будут искоючены при первой возможности.
  * Участник может находится в чёрном списке временно или бессрочно.
- Система времнных предупреждений с указнием причины.
- Снять определённое кол-во предупреждений со всех участников.
- Система "святых" участников, на которые не распроостраняются наказания,
  кроме исключения

обработчик событий:
- Отлов вышедших участников.
- Исключение заблокированных пользователей.
- Ежедневная обработка счётчиков.
- Подсчёт сообщений, слов, еженевная выдача жетонов активности.
- Приветствие тех, кто не писал больше дня.
- Сборщик мусора. (к прмеру просроченные заявки на брак)
- Тихий режим, чтобы не выводить собщение с итогами обработчика.

Author: Milinuri Nirvalen
Ver: 3.2.3 (34)
"""

from chiori import Config
from chiori import Plugin
from chiori import fcheck
from libs.chat_control import add_warn
from libs.chat_control import chat_data
from libs.chat_control import check_chat
from libs.chat_control import get_chat
from libs.chat_control import get_chat_ids
from libs.chat_control import get_str_time
from libs.chat_control import kick
from libs.chat_control import remove_warn
from libs.utils import parse_timedelta
from libs.utils import clear_text
from libs.utils import gen_code
from libs.vk import args
from libs.vk import get_id
from libs.vk import get_ids
from libs.vk import get_profiles

import time
from datetime import datetime


p = Plugin(name='чат: контроллер', backend='vk', path='data/chats' ,
	desc='главный модуль контроля чата, содержащий основные команды.',
	args={
		'cid':'(chat_id) id чата (200000000 + id)',
		'key':'ключ, имя данных (warns, counters...)',
		'edit_code': 'код редактироваия беседы (/chat)',
		'target':args["target"],
		'date':'время в фомате ЧИСЛО МНОЖИТЕЛЬ (минут, часов, дней, дней...)',
		'count':'количество'
	})

p.check_funcs['check_chat'] = check_chat


# handlers
# ========

@p.eventHandler('before')
async def _(event, ctx):
	if event.get('to.is_chat') and get_chat(event).file_data and event.get('to.profiles'):

		# определяем переменные
		c = get_chat(event)
		profiles = event.get('to.profiles')
		uid = str(event.get('from.id'))
		text = ''


		# обработка старыз типов данных
		# ==============================

		if isinstance(c.file_data["holy_members"], list):
			end_time = ctx.now + 2592000
			hm = {x: end_time for x in c.file_data["holy_members"]}
			c.file_data["holy_members"] = hm


		# кол-во сообщения в минуту + последнее сообщение
		# ===============================================

		if uid not in c.file_data["last_nessages"]:
			c.file_data['last_nessages'][uid] = [ctx.minutes, 1]
		elif c.file_data["last_nessages"][uid][0] == ctx.minutes:
			c.file_data["last_nessages"][uid][1] += 1
		else:
			c.file_data['last_nessages'][uid] = [ctx.minutes, 1]

		# if not c.file_data["silent_mode"] and c.file_data['last_nessages'][uid][1] > 7 and uid not in c.file_data['holy_members']:
		# 	text += await setWarn(c, profiles, [uid])
		# 	text += f'\nпревышена скорость сообщений. Пожалуйста не пишите с такой скоростью. Подождите минутку для сброса счётчика.'


		# автоматичский кик забаненных
		# ============================

		if event.get('action'):
			a = event.get('action')

			if a["type"] == 'chat_invite_user':
				uid = str(a["member_id"])
				if uid in c.file_data['banned'] and uid not in c.file_data["holy_members"]:
					await kick(event, ctx, [str(a["member_id"])])
					text += f'\n[*] пользователь находится в чёрном списке чата'

					if c.file_data["banned"][str(a["member_id"])]:
						t = c.file_data["banned"][str(a["member_id"])]
						text += f' до: {time.ctime(t)}'

			elif a["type"] == 'chat_kick_user':
				uid = str(a["member_id"])
				c.file_data["leaves"][uid] = ctx.now


		# ежедневная обработка счётчиков
		# ==============================

		if c.file_data['timers']['day'] <= ctx.minutes:
			if not c.file_data["silent_mode"]:
				text += f'\n[ обновление жетонов ]:'

			# преобразование жетонов
			for k, v in list((c.file_data["tokens"].items())):
				if isinstance(v, int):
					c.file_data["tokens"][k] = [round(v/7) for x in range(7)]

			tokens = sum(map(sum, c.file_data["tokens"].values())) or 1
			l = 0

			# вычет жетонов
			for k, v in list((c.file_data["tokens"].items())):

				l += v[0]
				v.pop(0)
				c.file_data['tokens'][k] = v

			if not c.file_data["silent_mode"]:
				text += f'\n-- 🔥было сожжено 🪙{l} жетонов (-{round(l/tokens*100, 2)}%)'

			# добавление жетонов
			a = 0
			if not c.file_data['tokens']:
				add_all = True
			else:
				add_all = False

			if add_all:
				for k, v in profiles.items():
					c.file_data['tokens'][k] = [1, 2, 3, 4, 5, 6, 7]

			for k, v in c.file_data['counters'].items():

				if not k in c.file_data['tokens']:
					c.file_data['tokens'][k] = [0, 0, 0, 0, 0, 0, round(sum(v[-1])/10)]
				else:
					c.file_data['tokens'][k].append(round(sum(v[-1])/10))


				a += round(sum(v[-1])/10)
				c.file_data['counters'][k].pop(0)
				c.file_data['counters'][k].append([0, 0])

			if not c.file_data["silent_mode"]:
				text += f'\n-- за активность выдано 🪙{a} жетонов (+{round(a/tokens*100, 2)}%)'
			c.file_data['timers']['day'] = ctx.minutes + 60*60*22
			c.file_data['edit_code'] = str(gen_code(6))

			c.file_data['tokens_history'].pop(0)
			tokens = sum(map(sum, c.file_data["tokens"].values())) or 1
			c.file_data['tokens_history'].append([tokens, l, a])


		# очистка текста + обработка счётчиков
		# ====================================

		ctext = clear_text(event.get('text', ''))
		words = [w for w in ctext.split(' ') if len(w) >= 4]

		if len(ctext) > 5 and len(words) > 2:

			if uid not in c.file_data['counters']:
				c.file_data['counters'][uid] = [ [0, 0] for x in range(300)]

			# добавление для счётчиков сообщений
			c.file_data['counters'][uid][-1][0] += 1
			c.file_data['counters'][uid][-1][1] += len(words)


		# сборщики мусора
		# ===============

		for k, v in list(c.file_data["holy_members"].items()):
			if ctx.now >= v:
				del c.file_data["holy_members"][k]
				if k in profiles:
					name = profiles[k]["name"]
				else:
					name = k

				text += f'\n-- {name} закончился срок действия роли.'


		for k, v in list(c.file_data["last_nessages"].items()):
			if k not in profiles:
				del c.file_data["last_nessages"][k]

		for m, d in list(c.file_data["warns"].items()):
			for k, v in list(d.items()):
				if int(k) <= ctx.now:
					if m in profiles:
						text += f'\n-- [{profiles[m]["name"]}] было снять предупрждение за {v}'
					del c.file_data["warns"][m]


		ids = {}
		for x in ['warns', 'tokens']:
			for k, v in list(c.file_data[x].items()):
				if isinstance(v, list):
					data = sum(v)
				else:
					data = v

				if not data:
					if k in ids:
						ids[k].append(x)
					else:
						ids[k] = [x]
					del c.file_data[x][k]


		if ids and not c.file_data["silent_mode"]:
			gc_profiles = await get_profiles(event, ctx, ids.keys())

			text += f'\n\n[ сборщик мусора ]'
			for k, v in ids.items():
				pr = gc_profiles[k]
				text += f'\n-- {pr["name"]}: {",".join(v)}'

		# проверка актуальности блокировки
		for k, v in list(c.file_data['banned'].items()):
			if v and ctx.minutes >= v:
				del c.file_data['banned'][k]
				text += f'\nу {v} истёк срок временного бана.'

		# очистка запросов на брак
		for k, v in list(c.file_data["m_requests"].items()):
			for x, t in list(v.items())	:
				if ctx.minutes-t >= 300:
					del c.file_data["m_requests"][k][x]
					text += f'{profiles[x]["name"]}. истекло время ожидания ответа от {profiles[k]["name"]}'
			if not v:
				del c.file_data["m_requests"][k]

		# приветствие неактивных участников
		lm = ctx.now - c.file_data['last_nessages'].get(uid, [ctx.now])[0]

		if lm > 360*24:
			h = datetime.now().strftime('%H')
			if h in range(0, 8):
				phrase = '🌙 доброй ночи'
			elif h in range(8, 12):
				phrase = '🌆 доброе утро'
			elif h in range(12, 18):
				phrase = '🌤 добрый день'
			else:
				phrase = "🌇 добрый вечер"

			text += f'{phrase}, {profiles[uid]["name"]}!\n💡 вы не появлялись в беседе {get_str_time(lm)}.'


		c.save()
		if text:
			await ctx.message(text)


# commands / настройка чата
# =========================

@p.command('<check chat/c>', usage='проверить чат на требования',
	check_func='check_chat', else_func='userNotAdmin')
async def checkChat(event, ctx):
	await ctx.message('Всё работает!')


@p.command('<connect/conn chat/c>', usage='<cid> привязать чат',
	check_func='check_chat', else_func='userNotAdmin')
async def connectChat(event, ctx):
	c = get_chat(event).file_data
	m = Config(filepath='data/chats/mirror.toml')
	chats = get_chat_ids()

	if len(ctx.args):
		if ctx.args[0] in chats:
			m.file_data[str(event.get('to.id'))] = ctx.args[0]
			m.save()
			await ctx.message(f'ваш чат был привязан к "{ctx.args[0]}"')
		else:
			await ctx.message(f'чат "{ctx.args[0]}" не найден')
	else:
		await ctx.message('для привязки чата необходимо указать id требуемого чата')


@p.command('<unconnect/unconn chat/c>', usage='<cid> отвзяать чат',
	check_func={'check_chat':1}, else_func='userNotAdmin')
async def unConnectChat(event, ctx):
	c = get_chat(event).file_data
	m = Config(filepath='data/chats/mirror.toml')

	if c["is_mirror"]:
		del m.file_data[str(event.get('to.id'))]
		m.save()
		await ctx.message(f'ваш чат был отвязан, удачного плавания.')
	else:
		await ctx.message('ваш чат не является зеркалом')


@p.command('reset', usage='<edit_code> <key> сбросить параметр чата',
	check_func='check_chat', else_func='userNotAdmin')
async def resetParams(event, ctx):
	c = get_chat(event)
	edit_code = 0
	text = f'сброс параметров чата:'

	if len(ctx.args) > 1:
		for a in ctx.args:
			if a.isdigit():
				edit_code = a

			elif edit_code == c.file_data["edit_code"]:
				if a in chat_data:
					c.file_data[a] = chat_data[a]
					text += f'\n-- "{a}" сброшено'
				else:
					text += f'\n-- "{a}" не найден параметр'
			else:
				text += f'\n-- "{a}" неправильный код редактирования'
		c.save()
		await ctx.message(text)

	else:
		await ctx.message('для сброса параметра, необходимо указать его названият')


@p.command('<s(ilent) mode>', 'тихий-режим',
	usage='переключить* тихий режим чата',
	check_func='check_chat', else_func='userNotAdmin')
async def swithSilentMode(event, ctx):
	c = get_chat(event)

	if c.file_data['silent_mode']:
		text = f'❌ тихий режим: выключен\nВы будете получать ежедневные оповещения об обновлениях статистики.'
		c.file_data['silent_mode'] = False
	else:
		text = f'✅ тихий режим: включён\nВы бельше не будете получать оповещения об обновлениях статистики.'
		c.file_data['silent_mode'] = True

	c.save()
	await ctx.message(text)


# connabds / кик участников
# =========================

@p.command('kick', 'кик', usage='<leaves/inactive/targets> исключить пользлвателя из чата',
	check_func={'check_chat':1}, else_func='userNotAdmin')
async def kickUser(event, ctx):
	c = get_chat(event)
	text = ''

	# кик вышедших участников
	if ctx.sargs in ['leaves', 'вышедшие', 'вышедших']:
		targets = c.file_data["leaves"].keys()

		if not targets:
			text += 'в беседе нету вышедших.\n'
		else:
			c.file_data["leaves"] = {}
			c.save()

	# кик неактивнфх участников
	elif ctx.sargs in ['неактив', 'inactive']:
		targets = []
		for k in event.get('to.profiles'):
			if k not in c.file_data['last_nessages']:
				targets.append(k)

		if not targets:
			text += f'в беседе нет неактивных участников\n'
	else:
		targets = await get_ids(event, ctx)


	if not targets:
		text += f'вышедшие/leaves; неактив/inactive\n{args["target"]}'
	else:
		text = await kick(event, ctx, targets)
	await ctx.message(text)


@p.command('кикот',
	usage='<targets> исключить приглашённых участников',
	check_func={'check_chat':1}, else_func='userNotAdmin')
async def kickFrom(event, ctx):
	c = get_chat(event)
	profiles = await get_profiles(event, ctx)
	targets = await get_ids(event, ctx)
	text = f'исключение участников ({len(targets)}):'

	for x in targets:
		text += f'\n от {profiles[x]["name"]}:'

		ids = []
		for k, v in profiles.items():
			if str(v["invited_by"]) == x:
				ids.append(k)

		if ids:
			text += '\n' + await kick(event, ctx, ids)

	await ctx.message(text)


# @p.command('кикпосле',
# 	usage='[date] исключение участников, которые появились впервые за указанное время',
# 	check_func={'check_chat':1}, else_func='userNotAdmin')
# async def kickSince(event, ctx):
# 	c = get_chat(event)
# 	date = ctx.now - add_time(*ctx.args)
# 	text = f'исключение участников живущих до {ctx.sargs}:'

# 	ids = []
# 	for k, v in event.get('to.profiles').items():
# 		if v["join_date"] > date:
# 			ids.append(k)

# 	if ids:
# 		text += '\n' + await kick(event, ctx, ids)
# 	await ctx.message(text)


# commands / бан/разбан участника
# ===============================

@p.command('ban', 'бан',
	usage='<target> [date/inf] добавить пользователя в чёрный список чата',
	check_func='check_chat', else_func='userNotAdmin')
async def banChatUser(event, ctx):
	c = get_chat(event)
	profiles = event.get('to.profiles')
	target = None
	if ctx.args:
		target, args = await get_id(event, ctx, ctx.args)

	if target and target in profiles:
		pr = profiles[target]
		if not pr.get('is_admin'):
			if target not in c.file_data['holy_members']:
				if len(ctx.args) > 1:
					date = ctx.now+parse_timedelta(args)
					str_time = f'до {time.ctime(date)}'
				else:
					date = 0
					str_time = ''
				c.file_data['banned'][target] = date
				c.save()

				await kick(event, ctx, target)
				await ctx.message(f'{pr["name"]} заблокирован {str_time}.')
			else:
				await ctx.message(f'{pr["name"]} святой(ая)')
		else:
			await ctx.message(f'{pr["name"]} администратор')
	else:
		await ctx.message(f'Вы не указали участника.')


@p.command('вечнобан', 'permban',
	usage='<targets> безсрочно заблокировать участников',
	check_func='check_chat', else_func='userNotAdmin')
async def permBanUser(event, ctx):
	c = get_chat(event)
	profiles = event.get('to.profiles')
	targets = await get_ids(event, ctx)
	text = f'блокировка участников ({len(targets)}):'

	for target in targets:
		if target not in profiles:
			text += f'\n-- [{target}] отцуцтвует в чате'
			continue

		pr = profiles[target]
		if pr.get('is_admin'):
			text += f'\n-- {pr["name"]} администратор чата'
			continue

		if target in c.file_data['holy_members']:
			text += f'\n-- {pr["name"]} святой(ая)'

		c.file_data['banned'][target] = 0
		await kick(event, ctx, target)
		text += f'\n-- {pr["name"]} заблокирован.'

	c.save()
	await ctx.message(text)


@p.command('unban', 'унбан',
	usage='<targets> удалить пользователлей из чёрного списка',
	check_func='check_chat', else_func='userNotAdmin')
async def unbanChatUser(event, ctx):
	c = get_chat(event)
	targets = await get_ids(event, ctx)
	profiles = await get_profiles(event, ctx, targets)
	text = 'разблокировка участников: '

	for target in targets:
		pr = profiles[target]

		if target in c.file_data['banned']:

			del c.file_data['banned'][target]
			text += f'\n-- {pr["name"]} разблокирован.'

		else:
			text += f'\n-- {pr["name"]} не находится в чёрном списке.'

	c.save()
	await ctx.message(text)


@p.command('banlist', 'банлист',
	usage='список заблокированых пользователей',
	check_func={'check_chat':1}, else_func='userNotAdmin')
async def banList(event, ctx):
	c = get_chat(event).file_data
	text = f'заблокированные пользователи ({len(c["banned"])}):'

	if c['banned']:
		profiles = await get_profiles(event, ctx, c["banned"].keys())

		for k, v in sorted(c["banned"].items(), key=lambda x: x[1]):
			pr = profiles[k]

			if v:
				t = time.ctime(v)
				text += f'\n-- [id{k}|{pr["name"]}] -> {t}'
			else:
				text += f'\n-- [id{k}|{pr["name"]}]'

	await ctx.message(text)


# commands / предупрждения
# ========================

@p.command('warn', 'варн', 'пред',
	usage='<targets> [count/1] выдать предупрждения',
	check_func='check_chat', else_func='userNotAdmin')
async def warnUser(event, ctx):
	c = get_chat(event)
	split_lines = ctx.sargs.split('\n', 1)
	added = parse_timedelta(split_lines[0])

	if len(split_lines) == 2:
		arg = split_lines[1]
	else:
		arg = 'причина не указана'

	await ctx.message(
		await add_warn(c, event.get('to.profiles'), ctx.now+added,
				await get_ids(event, ctx), arg))


@p.command('unwarn', 'ун(варн/пред)', 'раз(варн/пред)',
	usage='<targets> [count/1] снять предупреждения',
	check_func='check_chat', else_func='userNotAdmin')
async def unwarnUser(event, ctx):
	c = get_chat(event)
	count = 1
	args = ctx.args

	for a in args:
		if a.isdigit() and int(a) != 0 and int(a) <= 10:
			count = int(a)
			args.remove(a)

	await ctx.message(
		await remove_warn(c, event.get('to.profiles'),
				await get_ids(event, ctx, args), count))


@p.command('warnlist', 'warns', 'варнлист', 'пред(ы/лист)',
	usage='список предупрждений пользователям',
	check_func={'check_chat':1}, else_func='userNotAdmin')
async def warnList(event, ctx):
	c = get_chat(event).file_data
	all_warns = sum(map(len, c["warns"].values()))
	text = f'предупрждения пользователей ({all_warns}):'
	targets = await get_ids(event, ctx)
	profiles = await get_profiles(event, ctx, c["warns"].keys())

	if targets:
		for x in targets:
			if x not in profiles:
				text += f'\n-- [{x}]: отцуцтвуют предупрждения'
				continue

			pr = profiles[x]
			text += f'\n[ {pr["name"]} ]:'

			for k, v in sorted(c["warns"][x].items()):
				text += f'\n-- [{get_str_time(int(k) - ctx.now)}]: {v}'

	elif c['warns']:

		for k, v in sorted(c["warns"].items(), key=lambda x: x[1], reverse=True):
			pr = profiles[k]
			text += f'\n-- [id{k}|{pr["name"]}] -> {len(v)}'

	await ctx.message(text)


@p.command('unwarnall', 'снятьпреды',
	usage='[count/1] снять count предупреждений со всех участников чата',
	check_func='check_chat', else_func='userNotAdmin')
async def unwarnAll(event, ctx):
	c = get_chat(event)
	profiles = await get_profiles(event, ctx, c.file_data["warns"].keys())
	args = ctx.args
	count = 1

	for a in args:
		if a.isdigit() and int(a) != 0 and int(a) <= 10:
			count = int(a)
			args.remove(a)

	await ctx.message(
		await setWarn(c, profiles, c.file_data["warns"].keys(), -count))


# святык / иммунитеты
# ===================

@p.command('святые', 'holy', usage='список "святых" беседы',
	check_func={'check_chat':0}, else_func='chioNotAdmin')
async def holyMembers(event, ctx):
	c = get_chat(event).file_data['holy_members']
	text = f'"святые" беседы ({len(c)}):'

	if c:

		profiles = await get_profiles(event, ctx, c.keys())
		for k, v in sorted(c.items(), key=lambda x: x[1]):
			pr = profiles[k]

			text += f'\n-- [{get_str_time(v-ctx.now)}]: {pr["name"]}'


	text += f'\n\nсвятые -- группа участников, на которых не распространяются наказания.\nЗа использование полномочий в карыстых целях эту роль могут снять.'
	await ctx.message(text)


@p.command('<a/a(dd/ssig) role>', usage='<targets> назначить на роль святых',
	check_func='check_chat', else_func='userNotAdmin')
async def assignRole(event, ctx):
	c = get_chat(event)
	added = parse_timedelta(ctx.sargs)
	targets = await get_ids(event, ctx)
	profiles = event.get('to.profiles')
	text = f'назначение святых ({len(targets)}):'

	for target in targets:
		if target not in profiles:
			text += f'\n-- {target} нет в чате'
			continue

		pr = profiles[target]
		end_time = ctx.now + added
		c.file_data['holy_members'][target] = end_time
		text += f'\n-- {pr["name"]} выдана роль до {time.ctime(end_time)}'

	if targets:
		c.save()

	await ctx.message(text)


@p.command('<rm/remove role>', usage='<targets> снять с роли святых',
	check_func='check_chat', else_func='userNotAdmin')
async def removeRole(event, ctx):
	c = get_chat(event)
	targets = await get_ids(event, ctx)
	profiles = event.get('to.profiles')
	text = f'снятие святых:'

	for target in targets:
		if target not in profiles:
			text += f'\n-- {target} нет в чате'
			continue

		pr = profiles[target]

		if target in c.file_data['holy_members']:
			del c.file_data['holy_members'][target]
			text += f'\n-- {pr["name"]} снят(а) с роли'
		else:
			text += f'\n-- {pr["name"]} не был святым(ой)'

	if targets:
		c.save()
	await ctx.message(text)


@p.command('вышедшие', usage='список вышедших участников',
	check_func='check_chat', else_func='userNotAdmin')
async def leaves(event, ctx):
	c = get_chat(event).file_data['leaves']

	if c:
		profiles = await get_profiles(event, ctx, c.keys())
		text = f'вышедшие участники ({len(c)}):'

		for k, v in sorted(c.items(), key=lambda x: x[1]):
			pr = profiles[k]
			text += f'\n-- [{get_str_time(ctx.now-v)}]: [id{k}|{pr["name"]}]'
	else:
		text = 'в чата нету вышедших участников'

	await ctx.message(text)
